package com.fuint.common.aspect;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.fuint.common.util.LogUtils;
import com.fuint.utils.JsonFormatUtil;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.Modifier;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.ArrayUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * 控制器日志
 */
@Slf4j
@Component
@Aspect
@ComponentScan
@EnableAspectJAutoProxy
public class LogAop {

    /**
     * 使用获取方法参数名称
     *
     * @param className  类名
     * @param methodName 方法名
     * @throws Exception Exception
     */
    private String[] getFieldsName(String className, String methodName) throws Exception {
        Class<?> clazz = Class.forName(className);
        String clazz_name = clazz.getName();
        ClassPool pool = ClassPool.getDefault();
        ClassClassPath classPath = new ClassClassPath(clazz);
        pool.insertClassPath(classPath);
        try {
            CtClass ctClass = pool.get(clazz_name);
            CtMethod ctMethod = ctClass.getDeclaredMethod(methodName);
            MethodInfo methodInfo = ctMethod.getMethodInfo();
            CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
            LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
            if (attr == null) {
                return null;
            }
            String[] paramsArgsName = new String[ctMethod.getParameterTypes().length];
            int pos = Modifier.isStatic(ctMethod.getModifiers()) ? 0 : 1;
            for (int i = 0; i < paramsArgsName.length; i++) {
                paramsArgsName[i] = attr.variableName(i + pos);
            }
            return paramsArgsName;
        } catch (Exception ex) {
            return null;
        }
    }

    /**
     * 判断是否为基本类型
     */
    private boolean isPrimite(Class<?> clazz) {
        if (clazz.isPrimitive() || clazz == String.class) {
            return true;
        } else {
            return false;
        }
    }

    private StringBuilder getAllQueryParams(StringBuilder builder, HttpServletRequest request) {
        Map<String, String[]> paramMap = request.getParameterMap();
        Set<Map.Entry<String, String[]>> entries = paramMap.entrySet();
        StringBuilder paramBuilder = new StringBuilder();
        if (CollUtil.isNotEmpty(entries)) {
            paramBuilder.append("Request Params: ");
            for (Map.Entry<String, String[]> entry : entries) {
                String paramName = entry.getKey();
                String[] paramValues = entry.getValue();
                paramBuilder.append(paramName).append("=").append(paramValues[0]).append("&");
            }
            builder.append(paramBuilder.substring(0, paramBuilder.length() - 1)).append("\n");
        }
        return builder;
    }


    private void getRequestUri(JoinPoint point, StringBuilder builder) {
        // 获取当前方法所属类中 @RequestMapping 的value值
        String rooPath = "";
        Class<?> targetClass = point.getTarget().getClass();
        if (targetClass.isAnnotationPresent(RequestMapping.class)) {
            RequestMapping classRequestMapping = targetClass.getAnnotation(RequestMapping.class);
            String[] classValues = classRequestMapping.value();
            if (classValues.length > 0) {
                rooPath = classValues[0];
            }
        }

        MethodSignature methodSignature = (MethodSignature) point.getSignature();
        Method method = methodSignature.getMethod();
        // 检查方法是否带有 @RequestMapping 注解
        if (method.isAnnotationPresent(RequestMapping.class)) {
            // 获取 @RequestMapping 注解实例
            RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
            // 获取注解的 value 值
            String[] values = requestMapping.value();
            builder.append("\n").append("Request API: ").append(rooPath).append(values[0]).append("\n");
        }
    }

    /**
     * 打印方法参数值  基本类型直接打印，非基本类型需要重写toString方法
     *
     * @param paramsArgsName  方法参数名数组
     * @param paramsArgsValue 方法参数值数组
     */
    private void handleParamAndHeader(StringBuilder builder, String[] paramsArgsName, Object[] paramsArgsValue, Parameter[] parameters) {
        if (ArrayUtils.isEmpty(paramsArgsName) || ArrayUtils.isEmpty(paramsArgsValue)) {
            return;
        }

        for (int i = 0; i < paramsArgsName.length; i++) {
            String name = paramsArgsName[i];
            Object value = paramsArgsValue[i];
            Parameter parameter = parameters[i];
            if (value instanceof HttpServletRequest) {
                HttpServletRequest request = (HttpServletRequest) value;
                String accessToken = request.getHeader("Access-Token");
                String merchantNo = request.getHeader("merchantNo");
                String storeId = request.getHeader("storeId");
                if (StrUtil.isNotEmpty(accessToken)) {
                    builder.append("Request Header AccessToken: ").append(accessToken).append("\n");
                }
                if (StrUtil.isNotEmpty(merchantNo)) {
                    builder.append("Request Header merchantNo: ").append(merchantNo).append("\n");
                }
                if (StrUtil.isNotEmpty(storeId)) {
                    builder.append("Request Header storeId: ").append(storeId).append("\n");
                }
                builder = getAllQueryParams(builder, request);
            }
            if (parameter.isAnnotationPresent(PathVariable.class)) {
                builder.append("Request PathVariable: ").append(value).append("\n");
            }
            if (parameter.isAnnotationPresent(RequestBody.class)) {
                builder.append("Request Body: ").append(JsonFormatUtil.toJsonStr(value)).append("\n");
            }
        }
    }

    public void getParamAndHeader(JoinPoint point, StringBuilder builder) {
        if (Objects.isNull(point)) {
            return;
        }
        try {
            // 类名
            String className = point.getTarget().getClass().getName();
            String methodName = point.getSignature().getName();
            MethodSignature methodSignature = (MethodSignature) point.getSignature();
            Method method = methodSignature.getMethod();
            Parameter[] parameters = method.getParameters();

            // 获取方法的参数值数组
            Object[] methodArgs = point.getArgs();

            // 获取方法参数名称
            String[] paramNames = getFieldsName(className, methodName);

            // 输出方法的参数名和参数值
            handleParamAndHeader(builder, paramNames, methodArgs, parameters);
        } catch (Exception e) {
            LogUtils.error("LogAOP Error:", e);
        }
    }

    public Object proceed(ProceedingJoinPoint joinPoint) throws Throwable {
        StringBuilder builder = new StringBuilder();
        builder.append("\n--------------");
        this.getRequestUri(joinPoint, builder);
        this.getParamAndHeader(joinPoint, builder);
        Object proceed = joinPoint.proceed();
        builder.append("Request Response:\n").append(proceed);
        builder.append("\n--------------");
        LogUtils.info(builder.toString());
        return proceed;
    }

    @Around("execution(* com.fuint.module.backendApi.controller.*.*(..))")
    public Object aroundBackendApi(ProceedingJoinPoint joinPoint) throws Throwable {
        return proceed(joinPoint);
    }

    @Around("execution(* com.fuint.module.clientApi.controller.*.*(..))")
    public Object aroundClientApi(ProceedingJoinPoint joinPoint) throws Throwable {
        return proceed(joinPoint);
    }
}
